Service Worker & Web Worker
Web Worker
一直以来,一个网页只会有两个线程:GUI 渲染线程和 JS 引擎线程。即便你的 JS 写得再天花乱坠,也只能在一个进程里面执行。然而,JS 引擎线程和 GUI 渲染线程是互斥的,因此在 JS 执行的时候,UI 页面会被阻塞住。为了在进行高耗时 JS 运算时,UI 页面仍可用,那么就得另外开辟一个独立的 JS 线程来运行这些高耗时的 JS 代码,这就是 Web Worker。
浏览器为 JavaScript 创造的多线程环境。可以新建并将部分任务分配到 worker 线程并行运行,线程之间独立运行,互不干扰。线程完全受主线程控制并且不能操作 dom,只有主线程可以操作 dom。当子线程执行完之后再回到主线程上,在这个过程中不影响主线程的执行。可通过自带的消息机制相互通信。
用法:
// 通过 `worker = new Worker(url)` 加载一个 JS 文件来创建一个 worker,同时返回一个 worker 实例。
const worker = new Worker("work.js");
// 向主进程推送消息, 通过 `worker.postMessage(data)` 方法来向 worker 发送数据。
worker.postMessage("Hello World");
// 监听主进程来的消息, 绑定 `worker.onmessage` 方法来接收 worker 发送过来的数据。
worker.onmessage = function (event) {
console.log("Received message " + event.data);
};
// 可以使用 `worker.terminate()` 来终止一个 worker 的执行。
worker.terminate();
//worker.js
onmessage = function (e) {
console.log(e.data); //Hello
postMessage("Hi"); //向主进程发送消息
};
限制:
- 同源限制
- 无法使用
document/window/alert/confirm - 无法加载本地资源
Web Worker 应用
- 处理密集型数学计算
- 大数据集排序
- 数据处理(压缩、音频分析、图像处理等)
- 高流量网络通信
Service Worker
Service Worker 是一个特殊的 Web Worker,独立于页面主线程运行,它能够拦截和处理网络请求,并且配合 Cache Storage API,开发者可以自由的对页面发 送的 HTTP 请求进行管理,这就是为什么 Service Worker (PWA) 能让 Web 站点离线的原因。
Service Worker 的工作方式也衍生出了几种不同的请求控制策略,network First,cache First,network Only,cache Only 和 fastest,对于不同类型的请求, 我们应该采取不同的策略,静态文件,我们可以选择 cache First 或者 fastest,甚 至 cache Only,对于依赖后端数据的 AJAX 请求,我们应该选择 network First 或者 network Only,保证数据的实时性。 Service Worker 从在浏览器注册到进入工作状态和最终销毁会经历不同的阶 段,下图比较清楚的画出了 Service Worker 的生命周期。
在整个生命周期中,Service Worker 会抛出不同的事件,如 install,active,fetch 等,可以通过 self.addEventListener 来监听。
比如,监听网络请求事件,这里我们采用的策略是 cache First。
// service-worker.js
self.addEventListener("fetch", (e) => {
e.respondWith(
caches.match(e.request).then((response) => {
return response || fetch(e.request);
})
);
});
Service Worker 它并不只是能够缓存离线文件,后台同步和 Web Push 都是依 赖于 Service Worker 工作的。
Service Worker 是运行在浏览器背后的 独立线程 ,一般可以用来实现缓存功能。使用 Service Worker 的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。
Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。以下是这个步骤的实现:
// index.js
if (navigator.serviceWorker) {
navigator.serviceWorker
.register("sw.js")
.then(function (registration) {
console.log("service worker 注册成功");
})
.catch(function (err) {
console.log("servcie worker 注册失败");
});
}
// sw.js
// 监听 `install` 事件,回调中缓存所需文件
self.addEventListener("install", (e) => {
e.waitUntil(
caches.open("my-cache").then(function (cache) {
return cache.addAll(["./index.html", "./index.js"]);
})
);
});
// 拦截所有请求事件
// 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据
self.addEventListener("fetch", (e) => {
e.respondWith(
caches.match(e.request).then(function (response) {
if (response) {
return response;
}
console.log("fetch source");
})
);
});
打开页面,可以在开发者工具中的 Application 看到 Service Worker 已经启动了

在 Cache 中也可以发现我们所需的文件已被缓存

当我们重新刷新页面可以发现我们缓存的数据是从 Service Worker 中读取的

PushNotification
Push Notification 其实是两个 API 的结合,Notification API 和 Push API。
Notification API 提供了开发者可以给用户发送通知的能力,包括申请显示 Notification API 已经获得了大多数平台的支持,包括 Chrome、Edge、Firefox、Safari 等的支持,比较可 惜的是,iOS Safari 至今还不支持。
Push API,则是让服务器能够向用户发送离线消息,即使用户当前并没有打开你的页面,甚至没有打开浏览器。浏览器在接到消息推送时,会唤醒对应的 Service Worker,并抛出 push 事件,开发者接收到事件之后调用 Notification API 弹出通知,这就完成了整个接受和展示的流程。
// service-worker.js
self.addEventListener("push", (event) => {
event.waitUntil(
// Process the event and display a notification.
self.registration.showNotification("Hey!")
);
});
self.addEventListener("notificationclick", (event) => {
// Do something with the event
event.notification.close();
});
self.addEventListener("notificationclose", (event) => {
// Do something with the event
});
在之前的分享和文章中,很少提及 Web Push,这并不是因为它不重要,而是因为它在国内被支持程度非常低,支持 Web Push 的成本比 App Manifest 或者 Service Worker 要高的多,它需要浏览器厂商提供消息推送服务,截止到本文截 稿,国内只有 UC 即将发布的 U2 内核的浏览器才支持 Web Push API,Chrome 也因为其 依赖的 FCM/GCM 无法访问而导致 Web Push 无法使用。 浏览器接收到离线消息需要完成两个过程:
- 浏览器订阅通知;
- 服务器发送通知。 浏览器订阅通知,是指开发者调用 API 在消息服务器注册,具体过程如下图 所示,通过服务器提供的 Public Key 从浏览器获取 Push Subscription 对象,Push Subcription 包含浏览器对应的消息服务器的地址和一些密钥认证数据,将它发送 到服务器,服务器将这些数据存储,发送离线消息需要使用到这些数据。订阅通知 的过程就完成了。 服务器发送通知,服务器用 Private Key 将消息加密发送到之前保存的 Push Subcription 中对应的消息服务器,消息服务器解密消息后将消息推送到用户的浏 览器,浏览器唤醒 Service Worker,这就完成了整个消息推送的过程。
除了 Web App Manifest、Service Worker、Push API 这三个关键的技术 外,PWA 还包含很多优化的准则,比如 PRPL 模式,App Shell 模型,Credential Management API,等等。PWA 不是某一种特定的技术,换句话来说,PWA 是采用各种技术达到站点用户体验非常好的 Web App。单单从技术上讲,已经能够很好地弥补 传统 Web 的劣势了。